/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Service implementation of server side Multicast service for GIMnet.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#define _GNU_SOURCE
#include <string.h>
#include "protocolDispatcher.h"
#include "protocol/fastProtocolHdr.h"
#include "protocol/hubctrlHdr.h"
#include "apmulticastframe.h"
#include "multicastcore.h"
#include "owndebug.h"
#include "ownutils.h"
#include "msgreplybuffer.h"
#include "apservicebase.h"
#include "apsecurity.h"
#include <signal.h>
#include <assert.h>
#include <strings.h>

//*****************************************************************************
//*****************************************************************************
#define GIMNETAP_MULTICAST_VERSIONSTRING "v0.0-Alpha"
//*****************************************************************************
//*****************************************************************************
static int estimatedMaxMulticastIds = 1000;
static int estimatedMaxSubscribersPerId = 100;

long unsigned int succesfull_query_count = 0;
long unsigned int failed_query_count = 0;
static int verbose = ODINFO;
static char targetAddress[255] = "";
static int targetTcpPort = 0; // Port to use
static char serviceImportFile[256] = "./service_defaults.multicast";
static TAPSecurityAuthentication credentials = { "clientnone", 
                                                 10, 
                                                 "apsnone", 
                                                 7, 
                                                 "apnone", 
                                                 6 };
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static void sanityCheck()
{
  assert(sizeof(TAPMulticastFrame) == SIZEOF_TAPMULTICASTFRAME);
  assert(sizeof(TAPMulticastCtrl) == SIZEOF_TAPMULTICASTCTRL);
  assert(sizeof(TAPMulticastPayload) == SIZEOF_TAPMULTICASTPAYLOAD);
}
//*****************************************************************************

int registerMulticastGroup(uint32_t mcast_id, 
                           uint64_t mcast_owner_key, 
                           uint64_t mcast_access_key,
                           const char * const type, 
                           const char * const description)
{
  int result = -1;
  EMulticastType mt;

  if (strcasecmp(type, "public") == 0) mt = KMulticastTypePublic;
  else if (strcasecmp(type,"private") == 0) mt = KMulticastTypePrivate;
  else {
    dPrint(ODTEST,"Invalid multicast type '%s' given! Ignore", type);
    return -1;
  }

  int r = multicastcoreRegisterMulticastId(mcast_id, mcast_owner_key, mcast_access_key, mt);
  if (r == 0) {
    dPrint(ODTEST,"Succesfully registered MulticastID '%08x' with OwnerKey: 0x%llx, AccessKey: 0x%llx, type:'%s' ('%s')",
           mcast_id, mcast_owner_key, mcast_access_key, type, description);
    
    result = 0;
  
  } else {
    dPrint(ODTEST,"Failed to register MulticastID '%08x' with OwnerKey: 0x%llx, AccessKey: 0x%llx, type:'%s'",
           mcast_id, mcast_owner_key, mcast_access_key, type);
  
  }


  return result;
}
//*****************************************************************************

int importServicesFromFile(const char *aFilename)
{
  int result = 0;
  char line[1024];
  FILE *f = fopen(aFilename, "rb");

  if (f) {
    do {
      char *s = fgets(line, sizeof(line), f);
      //     dPrint(ODTEST,"Line: '%s'", s);
      if (s) {
        // Got line.
        uint32_t mcast_id = 0;
        uint64_t mcast_owner_key = 0;
        uint64_t mcast_access_key = 0;
        char type[64] = "";
        char description[512] = "";
        int bcount=0;

        // Comment - ignore. (Simple; first char must be '#' to qualify as comment)
        if (s[0] == '#') continue;

        // Parse (simple)
        const int converted = sscanf(s, "%x %llx %llx %s%n", 
                                     &mcast_id, 
                                     (unsigned long long int *)&mcast_owner_key, 
                                     (unsigned long long int *)&mcast_access_key, 
                                     type, &bcount);
        if (converted == 4) {
          strcpy(description, line+bcount+1);
          const int len = strlen(description);
          if (len) description[len-1] = '\0';
        
          // Register
          registerMulticastGroup(mcast_id, mcast_owner_key, mcast_access_key, type, description);
        }
               
      }
      
    } while ( !feof(f) );
    
    fclose(f);

  } else {
    dPrint(ODTEST,"Failed to open file: '%s'", aFilename);
    result = -1;

  }


  return result;
}
//*****************************************************************************

static void processFastProtocolFrame(const TFastProtocolHeader *framehdr,
                                     const unsigned char *payload,
                                     const unsigned int payloadSize,
                                     void *userPtr)
{  
  //  dPrint(ODTEST,"FastProtocolFrame: %p, %p, %d",
  //         framehdr, payload, payloadSize);
  TProtocolDisp *link = (TProtocolDisp *)userPtr;
  unsigned char replybuffer[sizeof(TFastProtocolHeader) + 
                            sizeof(TAPMulticastFrame) + 
                            sizeof(TAPMulticastCtrl)];
  assert(replybuffer != NULL);
  TFastProtocolHeader *replyfpframe = (TFastProtocolHeader *)replybuffer;
  TAPMulticastFrame *replymcframe = (TAPMulticastFrame *)(replybuffer+sizeof(TFastProtocolHeader));
  TAPMulticastCtrl *replymcctrl = (TAPMulticastCtrl *)(replybuffer+sizeof(TFastProtocolHeader)+sizeof(TAPMulticastFrame));

  // Apply and switch fp
  *replyfpframe = *framehdr;
  replyfpframe->source_id = framehdr->target_id;
  replyfpframe->target_id = framehdr->source_id;
  replyfpframe->ttl = FASTPROTOCOL_DEFAULT_TTL;
  replyfpframe->flags = 0x0;
  replyfpframe->protocol_id.total_size = sizeof(replybuffer);


  // Check that payload size is atleast size of the main frame
  if (payloadSize >= sizeof(TAPMulticastFrame) &&
      ((TAPMulticastFrame *)payload)->service_id == GIMNETAP_SERVICE_MULTICAST_ID) {
    const TAPMulticastFrame *mcframe = (const TAPMulticastFrame *)payload;
    const TAPMulticastCtrl *mcctrl = (const TAPMulticastCtrl *)(payload+sizeof(TAPMulticastFrame));
    const TAPMulticastPayload *mcpayload = (const TAPMulticastPayload *)(payload+sizeof(TAPMulticastFrame));
    
    int result = -1;
    /*      dPrint(ODTEST,"Qualified message received from node 0x%08x",
            framehdr->source_id);*/
    
    // Switch according to request.
    switch(mcframe->request) {
    case KAPMulticastRequestCreateMulticastId:
      result = multicastcoreRequestCreateMulticastId(replymcframe, 
                                                     replymcctrl, 
                                                     mcframe, 
                                                     mcctrl, 
                                                     framehdr);
      break;
      
    case KAPMulticastRequestPublishPublic:
      result = multicastcoreRequestPublishPublic(replymcframe, 
                                                 replymcctrl, 
                                                 mcframe, 
                                                 mcctrl, 
                                                 framehdr);
      break;
      
    case KAPMulticastRequestPublishPrivate:
      result = -1;
      break;
      
    case KAPMulticastRequestUnpublish:
      result = -1;
      break;
      
    case KAPMulticastRequestSubscribe:
      //      dPrint(ODTEST,"Got KAPMulticastRequestSubscribe");
      result = multicastcoreRequestSubscribe(replymcframe, 
                                             replymcctrl, 
                                             mcframe, 
                                             mcctrl, 
                                             framehdr);
      break;
      
    case KAPMulticastRequestUnsubscribe:
      //dPrint(ODTEST,"Got KAPMulticastRequestUnsubscribe");
      result = multicastcoreRequestUnsubscribe(replymcframe, 
                                               replymcctrl, 
                                               mcframe, 
                                               mcctrl, 
                                               framehdr);
      break;
      
    case KAPMulticastRequestIdList:
      //dPrint(ODTEST,"Got KAPMulticastRequestIdList");
      result = -1;
      break;
      
    case KAPMulticastRequestMulticast:
      // NOTE: This function does its own sending, due to different sized frames
      //dPrint(ODTEST,"Got KAPMulticastRequestMulticast");
      result = multicastcoreRequestMulticast(replymcframe, 
                                             replymcctrl,
                                             link,
                                             mcframe, 
                                             mcctrl,
                                             mcpayload, 
                                             framehdr);
      break;
    default:
      //result = multicastcoreRequestUnknown(replymcframe, mcframe, framehdr);
      dPrint(ODWARN,"Unsupported Multicast RequestType %u",
             mcframe->request);
      
      break;
    }
    
    // Reply is sent on ERRORS and OK:s, on multicast; NOT!
    // (RequestMulticast returns 1 on silent-success)
    if (result <= 0) {
      if (result < 0) failed_query_count++;
      else succesfull_query_count++;

      protocolDispSendPacket(link, (void*)&replybuffer, sizeof(replybuffer));
    } else {
      succesfull_query_count++;
    }
    
    
  } else if (framehdr->flags & (FASTPROTOCOL_FLAG_ERROR|FASTPROTOCOL_FLAG_REPORT_ERROR)) {

    // Call unsubscribe from all, since the entry has failed.
    multicastcoreUnsubscribeNodeIdFromAll(framehdr->source_id);


  } else {
    dPrint(ODTEST,"Invalid frame received from %08x!", framehdr->source_id );
    dPrint(ODTEST,"Payload size was %u bytes, needed >= %u bytes", payloadSize,  sizeof(TAPMulticastFrame));
    dPrint(ODTEST,"Service ID was %x, was supposed to be %x", 
           ((TAPMulticastFrame *)payload)->service_id,
           GIMNETAP_SERVICE_MULTICAST_ID);
  }

}
//*****************************************************************************

static int InitializeService(TProtocolDisp *link)
{
  int result = 0;

  // Init
  multicastcoreInit(estimatedMaxMulticastIds, estimatedMaxSubscribersPerId);

  // Import (If first char is not '\0')
  if (serviceImportFile[0]) 
    result = importServicesFromFile(serviceImportFile);
  
  return result;
}
//*****************************************************************************

static void DeinitializeService(TProtocolDisp *link)
{
  // Deinit.
  multicastcoreDeinit();
}
//*****************************************************************************

static void GetQueryStats(long unsigned int *s_count,
                   long unsigned int *f_count)
{
  *s_count = succesfull_query_count;
  *f_count = failed_query_count;
}
//*****************************************************************************

static void showUsage()
{
}
//*****************************************************************************

int main(int argc, char *argv[]) 
{
  int paramParseLoop = 1;
  //  unsigned int service_address = 0x00000001;
  //  const char *authenticationFile = NULL;
  int r = 0;

  // FIRST OF ALL
  sanityCheck();

  
  // Set up utils (debugs mostly)
  debugInit();
  debugSetGlobalDebugLvl(verbose);
  debugRemoveMode( DEBUG_MODE_TIMESTAMP_HH_MM_SS | DEBUG_MODE_TIMESTAMP_HH_MM_SS_MS );
  debugAddMode( DEBUG_MODE_TIMESTAMP_DELTA_SS_US );

  // Now; Set to BLOCK ALL SIGNALS
  apservicebaseInitSignalHandler();
  
  // Set info
  apservicebaseSetServiceInfo(GIMNETAP_MULTICAST_VERSIONSTRING,
                              GIMNETAP_SERVICE_MULTICAST_ID & 0xFF,
                              GIMNETAP_SERVICE_MULTICAST_ID);
  apservicebaseSetHubCtrlKey((uint8_t*)"apsnone", 7); // Default; command line overrides.
  apservicebaseSetDefaultOperationTimeout(2000);


  // Call registers
  apservicebaseRegisterFastProtocolHandler(processFastProtocolFrame);
  apservicebaseRegisterServiceInitHandler(InitializeService);
  apservicebaseRegisterServiceDeinitHandler(DeinitializeService);
  apservicebaseRegisterGetQueryStats(GetQueryStats);



  // Print version
  printf("\nGIMnet Multicast Service %s\n\n", GIMNETAP_MULTICAST_VERSIONSTRING);
  
  
  // Decode command line parameters.
  while(paramParseLoop) {
    int c;
    c = getopt(argc, argv, "A:I:u:p:qvh");
    switch(c) {
      
    case 'p':
      // Port to use
      targetTcpPort = atoi(optarg);
      break;
      
    case 'u':
      // Host to
      strncpy(targetAddress, optarg, sizeof(targetAddress)-1);
      targetAddress[sizeof(targetAddress)-1] = '\0';
      break;

    case 'I':
      strcpy(serviceImportFile, optarg);
      break;
      

    case 'A': {
      printf("Using AuthenticationFile: '%s'\n", optarg);
      if (apsecurityReadAuthenticationFile(&credentials,
                                           optarg)) {
        printf("Failed to read given Authentication file '%s'! Fatal error, abort!\n", 
               optarg);
        exit(1);
      
      } else {
        apservicebaseSetHubCtrlKey(credentials.secretAPService,
                                   credentials.secretAPServiceSize);
        
      }
      break;
    }
      
      //////////////////////////////////
      //// GENERIC:                 ////
      //////////////////////////////////

    case 'v':
      // Increase only if not previously set to 0 (by 'quiet')
      if (verbose > 0) verbose++;
      debugSetGlobalDebugLvl(verbose);
      break;
      
    case 'q':
      verbose = 0;
      debugSetGlobalDebugLvl(verbose);
      debugSetLogFilename(NULL);
      break;
      
    case 'h':
    case '?':
      showUsage();
    exit(1);
    break;
    
    case -1:
      paramParseLoop = 0;
      break;
    }
  }

  // Now; got all parameters -> start processing
  // 1. Initialize TCP link
  TProtocolDisp link;
  int v = 0;
  v = protocolDispInit(&link);
  dPrint(ODTEST,"Connecting to '%s' at port %u", targetAddress, targetTcpPort);
  v &= protocolDispConnect(&link, targetAddress, targetTcpPort);
  if (v == PROTOCOLDISPATCHER_OK) {
    // OK!
    r = apservicebaseExecuteService(&link);

  } else {
    // FAILED!
    dPrint(ODSTATUS,"Failed to connect to GIMnetAP, fatal error");

  } 

  dPrint(ODSTATUS,"Multicast terminated.");

  return r;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
